/*
 * Copyright (c) Kumo Inc. and affiliates.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <melon/expected.h>

#include <melon/benchmark.h>
#include <melon/exception_wrapper.h>
#include <melon/init/init.h>
#include <melon/lang/keep.h>

enum class V {};
enum class E {};

MELON_ALWAYS_INLINE E check_melon_expected_coro_await_unexpected_i(E i) {
  using X = melon::Expected<V, E>;
  auto fun = [i]() -> X {
    co_await melon::makeUnexpected(i);
    melon::compiler_may_unsafely_assume_unreachable();
  };
  return fun().error();
}

MELON_ALWAYS_INLINE E check_melon_expected_coro_await_expected_error_i(E i) {
  using X = melon::Expected<V, E>;
  auto fun = [i]() -> X {
    co_await X{melon::makeUnexpected(i)};
    melon::compiler_may_unsafely_assume_unreachable();
  };
  return fun().error();
}

MELON_ALWAYS_INLINE E check_melon_expected_coro_return_unexpected_i(E i) {
  using X = melon::Expected<V, E>;
  auto fun = [i]() -> X { //
    co_return melon::makeUnexpected(i);
  };
  return fun().error();
}

MELON_ALWAYS_INLINE E check_melon_expected_coro_return_expected_error_i(E i) {
  using X = melon::Expected<V, E>;
  auto fun = [i]() -> X { //
    co_return X{melon::makeUnexpected(i)};
  };
  return fun().error();
}

extern "C" MELON_KEEP MELON_NOINLINE E
check_melon_expected_coro_await_unexpected(E i) {
  return check_melon_expected_coro_await_unexpected_i(i);
}

extern "C" MELON_KEEP MELON_NOINLINE E
check_melon_expected_coro_await_expected_error(E i) {
  return check_melon_expected_coro_await_expected_error_i(i);
}

extern "C" MELON_KEEP MELON_NOINLINE E
check_melon_expected_coro_return_unexpected(E i) {
  return check_melon_expected_coro_return_unexpected_i(i);
}

extern "C" MELON_KEEP MELON_NOINLINE E
check_melon_expected_coro_return_expected_error(E i) {
  return check_melon_expected_coro_return_expected_error_i(i);
}

extern "C" MELON_KEEP MELON_NOINLINE E
check_melon_expected_coro_await_unexpected_eptr( //
    melon::exception_wrapper eptr) {
  using X = melon::Expected<V, melon::exception_wrapper>;
  auto fun = [&]() -> X {
    co_await melon::makeUnexpected(std::move(eptr));
    melon::compiler_may_unsafely_assume_unreachable();
  };
  auto e = fun().error().get_exception<E>();
  return e ? *e : E{};
}

extern "C" MELON_KEEP MELON_NOINLINE E
check_melon_expected_coro_await_expected_error_eptr(
    melon::exception_wrapper eptr) {
  using X = melon::Expected<V, melon::exception_wrapper>;
  auto fun = [&]() -> X {
    co_await X{melon::makeUnexpected(std::move(eptr))};
    melon::compiler_may_unsafely_assume_unreachable();
  };
  auto e = fun().error().get_exception<E>();
  return e ? *e : E{};
}

extern "C" MELON_KEEP MELON_NOINLINE E
check_melon_expected_coro_return_unexpected_eptr(
    melon::exception_wrapper eptr) {
  using X = melon::Expected<V, melon::exception_wrapper>;
  auto fun = [&]() -> X { //
    co_return melon::makeUnexpected(std::move(eptr));
  };
  auto e = fun().error().get_exception<E>();
  return e ? *e : E{};
}

extern "C" MELON_KEEP MELON_NOINLINE E
check_melon_expected_coro_return_expected_error_eptr(
    melon::exception_wrapper eptr) {
  using X = melon::Expected<V, melon::exception_wrapper>;
  auto fun = [&]() -> X { //
    co_return X{melon::makeUnexpected(std::move(eptr))};
  };
  auto e = fun().error().get_exception<E>();
  return e ? *e : E{};
}

BENCHMARK(noop_i_0x100, iters) {
  while (iters--) {
    for (size_t i = 0; i < 0x100; ++i) {
      auto res = E(7);
      melon::compiler_must_not_elide(res);
    }
  }
}

BENCHMARK(await_unexpected_i_0x100, iters) {
  while (iters--) {
    for (size_t i = 0; i < 0x100; ++i) {
      auto res = check_melon_expected_coro_await_unexpected_i(E(7));
      melon::compiler_must_not_elide(res);
    }
  }
}

BENCHMARK(await_expected_error_i_0x100, iters) {
  while (iters--) {
    for (size_t i = 0; i < 0x100; ++i) {
      auto res = check_melon_expected_coro_await_expected_error_i(E(7));
      melon::compiler_must_not_elide(res);
    }
  }
}

BENCHMARK(return_unexpected_i_0x100, iters) {
  while (iters--) {
    for (size_t i = 0; i < 0x100; ++i) {
      auto res = check_melon_expected_coro_return_unexpected_i(E(7));
      melon::compiler_must_not_elide(res);
    }
  }
}

BENCHMARK(return_expected_error_i_0x100, iters) {
  while (iters--) {
    for (size_t i = 0; i < 0x100; ++i) {
      auto res = check_melon_expected_coro_return_expected_error_i(E(7));
      melon::compiler_must_not_elide(res);
    }
  }
}

BENCHMARK(noop_0x10, iters) {
  while (iters--) {
    for (size_t i = 0; i < 0x10; ++i) {
      auto res = std::invoke([]() MELON_NOINLINE { return E(7); });
      melon::compiler_must_not_elide(res);
    }
  }
}

BENCHMARK(await_unexpected_0x10, iters) {
  while (iters--) {
    for (size_t i = 0; i < 0x10; ++i) {
      auto res = check_melon_expected_coro_await_unexpected(E(7));
      melon::compiler_must_not_elide(res);
    }
  }
}

BENCHMARK(await_expected_error_0x10, iters) {
  while (iters--) {
    for (size_t i = 0; i < 0x10; ++i) {
      auto res = check_melon_expected_coro_await_expected_error(E(7));
      melon::compiler_must_not_elide(res);
    }
  }
}

BENCHMARK(return_unexpected_0x10, iters) {
  while (iters--) {
    for (size_t i = 0; i < 0x10; ++i) {
      auto res = check_melon_expected_coro_return_unexpected(E(7));
      melon::compiler_must_not_elide(res);
    }
  }
}

BENCHMARK(return_expected_error_0x10, iters) {
  while (iters--) {
    for (size_t i = 0; i < 0x10; ++i) {
      auto res = check_melon_expected_coro_return_expected_error(E(7));
      melon::compiler_must_not_elide(res);
    }
  }
}

BENCHMARK(await_unexpected_eptr, iters) {
  auto eptr = melon::make_exception_wrapper<E>(7);
  while (iters--) {
    auto res = check_melon_expected_coro_await_unexpected_eptr(eptr);
    melon::compiler_must_not_elide(res);
  }
}

BENCHMARK(await_expected_error_eptr, iters) {
  auto eptr = melon::make_exception_wrapper<E>(7);
  while (iters--) {
    auto res = check_melon_expected_coro_await_expected_error_eptr(eptr);
    melon::compiler_must_not_elide(res);
  }
}

BENCHMARK(return_unexpected_eptr, iters) {
  auto eptr = melon::make_exception_wrapper<E>(7);
  while (iters--) {
    auto res = check_melon_expected_coro_return_unexpected_eptr(eptr);
    melon::compiler_must_not_elide(res);
  }
}

BENCHMARK(return_expected_error_eptr, iters) {
  auto eptr = melon::make_exception_wrapper<E>(7);
  while (iters--) {
    auto res = check_melon_expected_coro_return_expected_error_eptr(eptr);
    melon::compiler_must_not_elide(res);
  }
}

int main(int argc, char** argv) {
  melon::Init init(&argc, &argv);
  melon::runBenchmarks();
  return 0;
}
